home *** CD-ROM | disk | FTP | other *** search
/ Ultra Pack / UltraComputing Partner Applications.iso / SunLabs / tclTK / src / tk4.0 / tkTextMark.c < prev    next >
C/C++ Source or Header  |  1995-06-28  |  18KB  |  606 lines

  1. /* 
  2.  * tkTextMark.c --
  3.  *
  4.  *    This file contains the procedure that implement marks for
  5.  *    text widgets.
  6.  *
  7.  * Copyright (c) 1994 The Regents of the University of California.
  8.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  9.  *
  10.  * See the file "license.terms" for information on usage and redistribution
  11.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12.  */
  13.  
  14. static char sccsid[] = "@(#) tkTextMark.c 1.11 95/06/28 10:42:39";
  15.  
  16. #include "tkInt.h"
  17. #include "tkText.h"
  18. #include "tkPort.h"
  19.  
  20. /*
  21.  * Macro that determines the size of a mark segment:
  22.  */
  23.  
  24. #define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
  25.     + sizeof(TkTextMark)))
  26.  
  27. /*
  28.  * Forward references for procedures defined in this file:
  29.  */
  30.  
  31. static void        InsertUndisplayProc _ANSI_ARGS_((TkText *textPtr,
  32.                 TkTextDispChunk *chunkPtr));
  33. static int        MarkDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr,
  34.                 TkTextLine *linePtr, int treeGone));
  35. static TkTextSegment *    MarkCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr,
  36.                 TkTextLine *linePtr));
  37. static void        MarkCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
  38.                 TkTextLine *linePtr));
  39. static int        MarkLayoutProc _ANSI_ARGS_((TkText *textPtr,
  40.                 TkTextIndex *indexPtr, TkTextSegment *segPtr,
  41.                 int offset, int maxX, int maxChars,
  42.                 int noCharsYet, Tk_Uid wrapMode,
  43.                 TkTextDispChunk *chunkPtr));
  44.  
  45. /*
  46.  * The following structures declare the "mark" segment types.
  47.  * There are actually two types for marks, one with left gravity
  48.  * and one with right gravity.  They are identical except for
  49.  * their gravity property.
  50.  */
  51.  
  52. Tk_SegType tkTextRightMarkType = {
  53.     "mark",                    /* name */
  54.     0,                        /* leftGravity */
  55.     (Tk_SegSplitProc *) NULL,            /* splitProc */
  56.     MarkDeleteProc,                /* deleteProc */
  57.     MarkCleanupProc,                /* cleanupProc */
  58.     (Tk_SegLineChangeProc *) NULL,        /* lineChangeProc */
  59.     MarkLayoutProc,                /* layoutProc */
  60.     MarkCheckProc                /* checkProc */
  61. };
  62.  
  63. Tk_SegType tkTextLeftMarkType = {
  64.     "mark",                    /* name */
  65.     1,                        /* leftGravity */
  66.     (Tk_SegSplitProc *) NULL,            /* splitProc */
  67.     MarkDeleteProc,                /* deleteProc */
  68.     MarkCleanupProc,                /* cleanupProc */
  69.     (Tk_SegLineChangeProc *) NULL,        /* lineChangeProc */
  70.     MarkLayoutProc,                /* layoutProc */
  71.     MarkCheckProc                /* checkProc */
  72. };
  73.  
  74. /*
  75.  *--------------------------------------------------------------
  76.  *
  77.  * TkTextMarkCmd --
  78.  *
  79.  *    This procedure is invoked to process the "mark" options of
  80.  *    the widget command for text widgets. See the user documentation
  81.  *    for details on what it does.
  82.  *
  83.  * Results:
  84.  *    A standard Tcl result.
  85.  *
  86.  * Side effects:
  87.  *    See the user documentation.
  88.  *
  89.  *--------------------------------------------------------------
  90.  */
  91.  
  92. int
  93. TkTextMarkCmd(textPtr, interp, argc, argv)
  94.     register TkText *textPtr;    /* Information about text widget. */
  95.     Tcl_Interp *interp;        /* Current interpreter. */
  96.     int argc;            /* Number of arguments. */
  97.     char **argv;        /* Argument strings.  Someone else has already
  98.                  * parsed this command enough to know that
  99.                  * argv[1] is "mark". */
  100. {
  101.     int c, i;
  102.     size_t length;
  103.     Tcl_HashEntry *hPtr;
  104.     TkTextSegment *markPtr;
  105.     Tcl_HashSearch search;
  106.     TkTextIndex index;
  107.     Tk_SegType *newTypePtr;
  108.  
  109.     if (argc < 3) {
  110.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  111.         argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
  112.     return TCL_ERROR;
  113.     }
  114.     c = argv[2][0];
  115.     length = strlen(argv[2]);
  116.     if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) {
  117.     if (argc > 5) {
  118.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  119.             argv[0], " mark gravity markName ?gravity?",
  120.             (char *) NULL);
  121.         return TCL_ERROR;
  122.     }
  123.     hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]);
  124.     if (hPtr == NULL) {
  125.         Tcl_AppendResult(interp, "there is no mark named \"",
  126.             argv[3], "\"", (char *) NULL);
  127.         return TCL_ERROR;
  128.     }
  129.     markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
  130.     if (argc == 4) {
  131.         if (markPtr->typePtr == &tkTextRightMarkType) {
  132.         interp->result = "right";
  133.         } else {
  134.         interp->result = "left";
  135.         }
  136.         return TCL_OK;
  137.     }
  138.     length = strlen(argv[4]);
  139.     c = argv[4][0];
  140.     if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) {
  141.         newTypePtr = &tkTextLeftMarkType;
  142.     } else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) {
  143.         newTypePtr = &tkTextRightMarkType;
  144.     } else {
  145.         Tcl_AppendResult(interp, "bad mark gravity \"",
  146.             argv[4], "\": must be left or right", (char *) NULL);
  147.         return TCL_ERROR;
  148.     }
  149.     TkTextMarkSegToIndex(textPtr, markPtr, &index);
  150.     TkBTreeUnlinkSegment(textPtr->tree, markPtr,
  151.         markPtr->body.mark.linePtr);
  152.     markPtr->typePtr = newTypePtr;
  153.     TkBTreeLinkSegment(markPtr, &index);
  154.     } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
  155.     if (argc != 3) {
  156.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  157.             argv[0], " mark names\"", (char *) NULL);
  158.         return TCL_ERROR;
  159.     }
  160.     for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
  161.         hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
  162.         Tcl_AppendElement(interp,
  163.             Tcl_GetHashKey(&textPtr->markTable, hPtr));
  164.     }
  165.     } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
  166.     if (argc != 5) {
  167.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  168.             argv[0], " mark set markName index\"", (char *) NULL);
  169.         return TCL_ERROR;
  170.     }
  171.     if (TkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) {
  172.         return TCL_ERROR;
  173.     }
  174.     TkTextSetMark(textPtr, argv[3], &index);
  175.     } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
  176.     for (i = 3; i < argc; i++) {
  177.         hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
  178.         if (hPtr != NULL) {
  179.         markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
  180.         if ((markPtr == textPtr->insertMarkPtr)
  181.             || (markPtr == textPtr->currentMarkPtr)) {
  182.             continue;
  183.         }
  184.         TkBTreeUnlinkSegment(textPtr->tree, markPtr,
  185.             markPtr->body.mark.linePtr);
  186.         Tcl_DeleteHashEntry(hPtr);
  187.         ckfree((char *) markPtr);
  188.         }
  189.     }
  190.     } else {
  191.     Tcl_AppendResult(interp, "bad mark option \"", argv[2],
  192.         "\":  must be gravity, names, set, or unset",
  193.         (char *) NULL);
  194.     return TCL_ERROR;
  195.     }
  196.     return TCL_OK;
  197. }
  198.  
  199. /*
  200.  *----------------------------------------------------------------------
  201.  *
  202.  * TkTextSetMark --
  203.  *
  204.  *    Set a mark to a particular position, creating a new mark if
  205.  *    one doesn't already exist.
  206.  *
  207.  * Results:
  208.  *    The return value is a pointer to the mark that was just set.
  209.  *
  210.  * Side effects:
  211.  *    A new mark is created, or an existing mark is moved.
  212.  *
  213.  *----------------------------------------------------------------------
  214.  */
  215.  
  216. TkTextSegment *
  217. TkTextSetMark(textPtr, name, indexPtr)
  218.     TkText *textPtr;        /* Text widget in which to create mark. */
  219.     char *name;            /* Name of mark to set. */
  220.     TkTextIndex *indexPtr;    /* Where to set mark. */
  221. {
  222.     Tcl_HashEntry *hPtr;
  223.     TkTextSegment *markPtr;
  224.     TkTextIndex insertIndex;
  225.     int new;
  226.  
  227.     hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
  228.     markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
  229.     if (!new) {
  230.     /*
  231.      * If this is the insertion point that's being moved, be sure
  232.      * to force a display update at the old position.  Also, don't
  233.      * let the insertion cursor be after the final newline of the
  234.      * file.
  235.      */
  236.  
  237.     if (markPtr == textPtr->insertMarkPtr) {
  238.         TkTextIndex index, index2;
  239.         TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
  240.         TkTextIndexForwChars(&index, 1, &index2);
  241.         TkTextChanged(textPtr, &index, &index2);
  242.         if (TkBTreeLineIndex(indexPtr->linePtr)
  243.             == TkBTreeNumLines(textPtr->tree))  {
  244.         TkTextIndexBackChars(indexPtr, 1, &insertIndex);
  245.         indexPtr = &insertIndex;
  246.         }
  247.     }
  248.     TkBTreeUnlinkSegment(textPtr->tree, markPtr,
  249.         markPtr->body.mark.linePtr);
  250.     } else {
  251.     markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE);
  252.     markPtr->typePtr = &tkTextRightMarkType;
  253.     markPtr->size = 0;
  254.     markPtr->body.mark.textPtr = textPtr;
  255.     markPtr->body.mark.linePtr = indexPtr->linePtr;
  256.     markPtr->body.mark.hPtr = hPtr;
  257.     Tcl_SetHashValue(hPtr, markPtr);
  258.     }
  259.     TkBTreeLinkSegment(markPtr, indexPtr);
  260.  
  261.     /*
  262.      * If the mark is the insertion cursor, then update the screen at the
  263.      * mark's new location.
  264.      */
  265.  
  266.     if (markPtr == textPtr->insertMarkPtr) {
  267.     TkTextIndex index2;
  268.  
  269.     TkTextIndexForwChars(indexPtr, 1, &index2);
  270.     TkTextChanged(textPtr, indexPtr, &index2);
  271.     }
  272.     return markPtr;
  273. }
  274.  
  275. /*
  276.  *--------------------------------------------------------------
  277.  *
  278.  * TkTextMarkSegToIndex --
  279.  *
  280.  *    Given a segment that is a mark, create an index that
  281.  *    refers to the next text character (or other text segment
  282.  *    with non-zero size) after the mark.
  283.  *
  284.  * Results:
  285.  *    *IndexPtr is filled in with index information.
  286.  *
  287.  * Side effects:
  288.  *    None.
  289.  *
  290.  *--------------------------------------------------------------
  291.  */
  292.  
  293. void
  294. TkTextMarkSegToIndex(textPtr, markPtr, indexPtr)
  295.     TkText *textPtr;        /* Text widget containing mark. */
  296.     TkTextSegment *markPtr;    /* Mark segment. */
  297.     TkTextIndex *indexPtr;    /* Index information gets stored here.  */
  298. {
  299.     TkTextSegment *segPtr;
  300.  
  301.     indexPtr->tree = textPtr->tree;
  302.     indexPtr->linePtr = markPtr->body.mark.linePtr;
  303.     indexPtr->charIndex = 0;
  304.     for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
  305.         segPtr = segPtr->nextPtr) {
  306.     indexPtr->charIndex += segPtr->size;
  307.     }
  308. }
  309.  
  310. /*
  311.  *--------------------------------------------------------------
  312.  *
  313.  * TkTextMarkNameToIndex --
  314.  *
  315.  *    Given the name of a mark, return an index corresponding
  316.  *    to the mark name.
  317.  *
  318.  * Results:
  319.  *    The return value is TCL_OK if "name" exists as a mark in
  320.  *    the text widget.  In this case *indexPtr is filled in with
  321.  *    the next segment whose after the mark whose size is
  322.  *    non-zero.  TCL_ERROR is returned if the mark doesn't exist
  323.  *    in the text widget.
  324.  *
  325.  * Side effects:
  326.  *    None.
  327.  *
  328.  *--------------------------------------------------------------
  329.  */
  330.  
  331. int
  332. TkTextMarkNameToIndex(textPtr, name, indexPtr)
  333.     TkText *textPtr;        /* Text widget containing mark. */
  334.     char *name;            /* Name of mark. */
  335.     TkTextIndex *indexPtr;    /* Index information gets stored here. */
  336. {
  337.     Tcl_HashEntry *hPtr;
  338.  
  339.     hPtr = Tcl_FindHashEntry(&textPtr->markTable, name);
  340.     if (hPtr == NULL) {
  341.     return TCL_ERROR;
  342.     }
  343.     TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr),
  344.         indexPtr);
  345.     return TCL_OK;
  346. }
  347.  
  348. /*
  349.  *--------------------------------------------------------------
  350.  *
  351.  * MarkDeleteProc --
  352.  *
  353.  *    This procedure is invoked by the text B-tree code whenever
  354.  *    a mark lies in a range of characters being deleted.
  355.  *
  356.  * Results:
  357.  *    Returns 1 to indicate that deletion has been rejected.
  358.  *
  359.  * Side effects:
  360.  *    None (even if the whole tree is being deleted we don't
  361.  *    free up the mark;  it will be done elsewhere).
  362.  *
  363.  *--------------------------------------------------------------
  364.  */
  365.  
  366.     /* ARGSUSED */
  367. static int
  368. MarkDeleteProc(segPtr, linePtr, treeGone)
  369.     TkTextSegment *segPtr;        /* Segment being deleted. */
  370.     TkTextLine *linePtr;        /* Line containing segment. */
  371.     int treeGone;            /* Non-zero means the entire tree is
  372.                      * being deleted, so everything must
  373.                      * get cleaned up. */
  374. {
  375.     return 1;
  376. }
  377.  
  378. /*
  379.  *--------------------------------------------------------------
  380.  *
  381.  * MarkCleanupProc --
  382.  *
  383.  *    This procedure is invoked by the B-tree code whenever a
  384.  *    mark segment is moved from one line to another.
  385.  *
  386.  * Results:
  387.  *    None.
  388.  *
  389.  * Side effects:
  390.  *    The linePtr field of the segment gets updated.
  391.  *
  392.  *--------------------------------------------------------------
  393.  */
  394.  
  395. static TkTextSegment *
  396. MarkCleanupProc(markPtr, linePtr)
  397.     TkTextSegment *markPtr;        /* Mark segment that's being moved. */
  398.     TkTextLine *linePtr;        /* Line that now contains segment. */
  399. {
  400.     markPtr->body.mark.linePtr = linePtr;
  401.     return markPtr;
  402. }
  403.  
  404. /*
  405.  *--------------------------------------------------------------
  406.  *
  407.  * MarkLayoutProc --
  408.  *
  409.  *    This procedure is the "layoutProc" for mark segments.
  410.  *
  411.  * Results:
  412.  *    If the mark isn't the insertion cursor then the return
  413.  *    value is -1 to indicate that this segment shouldn't be
  414.  *    displayed.  If the mark is the insertion character then
  415.  *    1 is returned and the chunkPtr structure is filled in.
  416.  *
  417.  * Side effects:
  418.  *    None, except for filling in chunkPtr.
  419.  *
  420.  *--------------------------------------------------------------
  421.  */
  422.  
  423.     /*ARGSUSED*/
  424. static int
  425. MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
  426.     noCharsYet, wrapMode, chunkPtr)
  427.     TkText *textPtr;        /* Text widget being layed out. */
  428.     TkTextIndex *indexPtr;    /* Identifies first character in chunk. */
  429.     TkTextSegment *segPtr;    /* Segment corresponding to indexPtr. */
  430.     int offset;            /* Offset within segPtr corresponding to
  431.                  * indexPtr (always 0). */
  432.     int maxX;            /* Chunk must not occupy pixels at this
  433.                  * position or higher. */
  434.     int maxChars;        /* Chunk must not include more than this
  435.                  * many characters. */
  436.     int noCharsYet;        /* Non-zero means no characters have been
  437.                  * assigned to this line yet. */
  438.     Tk_Uid wrapMode;        /* Not used. */
  439.     register TkTextDispChunk *chunkPtr;
  440.                 /* Structure to fill in with information
  441.                  * about this chunk.  The x field has already
  442.                  * been set by the caller. */
  443. {
  444.     if (segPtr != textPtr->insertMarkPtr) {
  445.     return -1;
  446.     }
  447.  
  448.     chunkPtr->displayProc = TkTextInsertDisplayProc;
  449.     chunkPtr->undisplayProc = InsertUndisplayProc;
  450.     chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL;
  451.     chunkPtr->bboxProc = (Tk_ChunkBboxProc *) NULL;
  452.     chunkPtr->numChars = 0;
  453.     chunkPtr->minAscent = 0;
  454.     chunkPtr->minDescent = 0;
  455.     chunkPtr->minHeight = 0;
  456.     chunkPtr->width = 0;
  457.  
  458.     /*
  459.      * Note: can't break a line after the insertion cursor:  this
  460.      * prevents the insertion cursor from being stranded at the end
  461.      * of a line.
  462.      */
  463.  
  464.     chunkPtr->breakIndex = -1;
  465.     chunkPtr->clientData = (ClientData) textPtr;
  466.     return 1;
  467. }
  468.  
  469. /*
  470.  *--------------------------------------------------------------
  471.  *
  472.  * TkTextInsertDisplayProc --
  473.  *
  474.  *    This procedure is called to display the insertion
  475.  *    cursor.
  476.  *
  477.  * Results:
  478.  *    None.
  479.  *
  480.  * Side effects:
  481.  *    Graphics are drawn.
  482.  *
  483.  *--------------------------------------------------------------
  484.  */
  485.  
  486.     /* ARGSUSED */
  487. void
  488. TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
  489.     TkTextDispChunk *chunkPtr;        /* Chunk that is to be drawn. */
  490.     int x;                /* X-position in dst at which to
  491.                      * draw this chunk (may differ from
  492.                      * the x-position in the chunk because
  493.                      * of scrolling). */
  494.     int y;                /* Y-position at which to draw this
  495.                      * chunk in dst (x-position is in
  496.                      * the chunk itself). */
  497.     int height;                /* Total height of line. */
  498.     int baseline;            /* Offset of baseline from y. */
  499.     Display *display;            /* Display to use for drawing. */
  500.     Drawable dst;            /* Pixmap or window in which to draw
  501.                      * chunk. */
  502.     int screenY;            /* Y-coordinate in text window that
  503.                      * corresponds to y. */
  504. {
  505.     TkText *textPtr = (TkText *) chunkPtr->clientData;
  506.     int halfWidth = textPtr->insertWidth/2;
  507.  
  508.     if ((x + halfWidth) <= 0) {
  509.     /*
  510.      * The insertion cursor is off-screen.  Just return.
  511.      */
  512.  
  513.     return;
  514.     }
  515.  
  516.     /*
  517.      * As a special hack to keep the cursor visible on mono displays
  518.      * (or anywhere else that the selection and insertion cursors
  519.      * have the same color) write the default background in the cursor
  520.      * area (instead of nothing) when the cursor isn't on.  Otherwise
  521.      * the selection might hide the cursor.
  522.      */
  523.  
  524.     if (textPtr->flags & INSERT_ON) {
  525.     Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
  526.         x - textPtr->insertWidth/2, y, textPtr->insertWidth,
  527.         height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
  528.     } else if (textPtr->selBorder == textPtr->insertBorder) {
  529.     Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
  530.         x - textPtr->insertWidth/2, y, textPtr->insertWidth,
  531.         height, 0, TK_RELIEF_FLAT);
  532.     }
  533. }
  534.  
  535. /*
  536.  *--------------------------------------------------------------
  537.  *
  538.  * InsertUndisplayProc --
  539.  *
  540.  *    This procedure is called when the insertion cursor is no
  541.  *    longer at a visible point on the display.  It does nothing
  542.  *    right now.
  543.  *
  544.  * Results:
  545.  *    None.
  546.  *
  547.  * Side effects:
  548.  *    None.
  549.  *
  550.  *--------------------------------------------------------------
  551.  */
  552.  
  553.     /* ARGSUSED */
  554. static void
  555. InsertUndisplayProc(textPtr, chunkPtr)
  556.     TkText *textPtr;            /* Overall information about text
  557.                      * widget. */
  558.     TkTextDispChunk *chunkPtr;        /* Chunk that is about to be freed. */
  559. {
  560.     return;
  561. }
  562.  
  563. /*
  564.  *--------------------------------------------------------------
  565.  *
  566.  * MarkCheckProc --
  567.  *
  568.  *    This procedure is invoked by the B-tree code to perform
  569.  *    consistency checks on mark segments.
  570.  *
  571.  * Results:
  572.  *    None.
  573.  *
  574.  * Side effects:
  575.  *    The procedure panics if it detects anything wrong with
  576.  *    the mark.
  577.  *
  578.  *--------------------------------------------------------------
  579.  */
  580.  
  581. static void
  582. MarkCheckProc(markPtr, linePtr)
  583.     TkTextSegment *markPtr;        /* Segment to check. */
  584.     TkTextLine *linePtr;        /* Line containing segment. */
  585. {
  586.     Tcl_HashSearch search;
  587.     Tcl_HashEntry *hPtr;
  588.  
  589.     if (markPtr->body.mark.linePtr != linePtr) {
  590.     panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
  591.     }
  592.  
  593.     /*
  594.      * Make sure that the mark is still present in the text's mark
  595.      * hash table.
  596.      */
  597.  
  598.     for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable,
  599.         &search); hPtr != markPtr->body.mark.hPtr;
  600.         hPtr = Tcl_NextHashEntry(&search)) {
  601.     if (hPtr == NULL) {
  602.         panic("MarkCheckProc couldn't find hash table entry for mark");
  603.     }
  604.     }
  605. }
  606.